/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#ifndef itkSobelOperator_hxx
#define itkSobelOperator_hxx

#include "itkObject.h"

namespace itk
{
template <typename TPixel, unsigned int VDimension, typename TAllocator>
void
SobelOperator<TPixel, VDimension, TAllocator>::Fill(const CoefficientVector & coeff)
{
  this->InitializeToZero();

  if constexpr (VDimension == 2 || VDimension == 3)
  {
    // Note that this code is only good for 2d and 3d operators.  Places the
    // coefficients in the exact center of the neighborhood
    const unsigned int center = this->GetCenterNeighborhoodIndex();

    if constexpr (VDimension == 3)
    {
      unsigned int coeff_index = 0;
      for (int z = -1; z <= 1; ++z)
      {
        for (int y = -1; y <= 1; ++y)
        {
          for (int x = -1; x <= 1; ++x)
          {
            const int pos = center + z * this->GetStride(2) + y * this->GetStride(1) + x * this->GetStride(0);

            this->operator[](pos) = static_cast<TPixel>(coeff[coeff_index]);

            ++coeff_index;
          }
        }
      }
    }
    else // So now VDimension == 2
    {
      unsigned int coeff_index = 0;
      for (int y = -1; y <= 1; ++y)
      {
        for (int x = -1; x <= 1; ++x)
        {
          const int pos = center + y * this->GetStride(1) + x * this->GetStride(0);
          // Note, The following line copies the double precision
          // coefficients of SobelOperator to the pixel type
          // of the neighborhood operator which may not support
          // negative numbers, or floating point numbers.
          this->operator[](pos) = static_cast<TPixel>(coeff[coeff_index]);

          ++coeff_index;
        }
      }
    }
  }
  else
  {
    itkExceptionMacro("The ND version of the Sobel operator is not yet implemented.  Currently only the 2D and 3D "
                      "versions are available.");
  }
}

template <typename TPixel, unsigned int VDimension, typename TAllocator>
auto
SobelOperator<TPixel, VDimension, TAllocator>::GenerateCoefficients() -> CoefficientVector
{
  CoefficientVector     coeff;
  constexpr std::size_t coeff_size = (VDimension == 2) ? 9 : 27;
  coeff.reserve(coeff_size);
  if (VDimension == 2 && this->GetDirection() == 0)
  {
    coeff.push_back(-1.0);
    coeff.push_back(0.0);
    coeff.push_back(1.0);
    coeff.push_back(-2.0);
    coeff.push_back(0.0);
    coeff.push_back(2);
    coeff.push_back(-1.0);
    coeff.push_back(0.0);
    coeff.push_back(1.0);
  }
  else if (VDimension == 2 && this->GetDirection() == 1)
  {
    coeff.push_back(-1.0);
    coeff.push_back(-2);
    coeff.push_back(-1.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(1.0);
    coeff.push_back(2);
    coeff.push_back(1.0);
  }
  else if (VDimension == 3 && this->GetDirection() == 0)
  {
    coeff.push_back(-1.0);
    coeff.push_back(0.0);
    coeff.push_back(1.0);
    coeff.push_back(-3.0);
    coeff.push_back(0.0);
    coeff.push_back(3.0);
    coeff.push_back(-1.0);
    coeff.push_back(0.0);
    coeff.push_back(1.0);

    coeff.push_back(-3.0);
    coeff.push_back(0.0);
    coeff.push_back(3.0);
    coeff.push_back(-6.0);
    coeff.push_back(0.0);
    coeff.push_back(6.0);
    coeff.push_back(-3.0);
    coeff.push_back(0.0);
    coeff.push_back(3.0);

    coeff.push_back(-1.0);
    coeff.push_back(0.0);
    coeff.push_back(1.0);
    coeff.push_back(-3.0);
    coeff.push_back(0.0);
    coeff.push_back(3.0);
    coeff.push_back(-1.0);
    coeff.push_back(0.0);
    coeff.push_back(1.0);
  }
  else if (VDimension == 3 && this->GetDirection() == 1)
  {
    coeff.push_back(-1.0);
    coeff.push_back(-3.0);
    coeff.push_back(-1.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(1.0);
    coeff.push_back(3.0);
    coeff.push_back(1.0);

    coeff.push_back(-3.0);
    coeff.push_back(-6.0);
    coeff.push_back(-3.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(3.0);
    coeff.push_back(6.0);
    coeff.push_back(3.0);

    coeff.push_back(-1.0);
    coeff.push_back(-3.0);
    coeff.push_back(-1.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(1.0);
    coeff.push_back(3.0);
    coeff.push_back(1.0);
  }
  else if (VDimension == 3 && this->GetDirection() == 2)
  {
    coeff.push_back(-1.0);
    coeff.push_back(-3.0);
    coeff.push_back(-1.0);
    coeff.push_back(-3.0);
    coeff.push_back(-6.0);
    coeff.push_back(-3.0);
    coeff.push_back(-1.0);
    coeff.push_back(-3.0);
    coeff.push_back(-1.0);

    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);
    coeff.push_back(0.0);

    coeff.push_back(1.0);
    coeff.push_back(3.0);
    coeff.push_back(1.0);
    coeff.push_back(3.0);
    coeff.push_back(6.0);
    coeff.push_back(3.0);
    coeff.push_back(1.0);
    coeff.push_back(3.0);
    coeff.push_back(1.0);
  }
  else
  {
    itkExceptionMacro("The ND version of the Sobel operator has not been implemented.  Currently only 2D and 3D "
                      "versions are available.");
  }

  return coeff;
}
} // namespace itk

#endif
